package com.dg.gaming.server.controller;

import java.util.*;

import com.dg.gaming.api.common.*;
import com.dg.gaming.api.common.msg.*;
import com.dg.gaming.api.common.util.*;
import com.dg.gaming.server.*;
import com.dg.gaming.server.data.*;
import nanoxml.*;

/**
 * <p>Title: </p>
 *
 * <p>Description: </p>
 *
 * <p>Copyright: Copyright (c) 2009</p>
 *
 * <p>Company: </p>
 *
 * @author not attributable
 * @version 1.0
 */
public abstract class ServerTableController {
    /**
     * Method which is called when a create game is created.
     *
     * @param tableNum  Table number.
     */
    public abstract void startGame(String tableNum);

    /**
     * Method which is called once after the game has started (after startGame()
     * above has been called) and after all of the clients have been told that
     * the game is starting.
     *
     * This method should be overridden by a game specific server controller
     * that wants to send out messages to clients that needs to happen only
     * once per game.  (For example, dealing out a hand of cards.)
     *
     * @param conn       The connection thread for the last player which
     *                     clicked the "Start" button.
     * @param tableNum   The table number.
     */
    public void sendInitialClientMessages(ServerTableHandler conn,
                                          String tableNum) {
    }

    /**
     * Method which is called when a game is over.
     *
     * @conn             Connection to client.
     * @param tableNum   Table number.
     * @param resultType Result type (e.g. win, lose etc)
     */
    public abstract void gameOver(ServerTableHandler conn, String tableNum,
                                  int resultType);

    /**
     * Resign method - the default behavior is that this users loses (and
     * so the other users win).  Over write this method if this isn't the
     * required functionality or you wish to record a score/history etc.
     *
     * @param conn      Connection to server
     * @param tableNum  Table number.
     */
    public void userResigns(ServerTableHandler conn, String tableNum) {
        gameOver(conn, tableNum, conn.getUserId(), IGameOver.LOSE);
    }

    /**
     * User agrees on a draw. The default behavior is that this user draws
     * (and so the other users draw also).  Over write this method if this
     * isn't the required functionality or you wish to record a score/history
     * etc.
     *
     * @param conn      Connection to server
     * @param tableNum
     */
    public void usersAgreeDraw(ServerTableHandler conn, String tableNum) {
        gameOver(conn, tableNum, conn.getUserId(), IGameOver.DRAW);
    }

    private ServerTableHandler contr = null;
//    private
    public ServerTableController(ServerTableHandler tableController) {
        this.contr = tableController;
    }

    /**
     * A class can override this method to provide custom properties of the game
     * that will be sent to all clients when they send the game_connect message.
     * Clients can use getCustomGameProperties() on the game to retrieve this XML tree.
     *
     * @return an XML tree of custom properties.
     */
    public XMLElement getCustomGameProperties() {
        return null;
    }

    /**
     * The class must implement this method which reads a message
     * from a client.
     *
     * @param conn     Connection from the server to a client.
     * @param message  Message from the client.
     */
    public void parseGameMessage(ServerTableHandler conn,
                                 XMLElement message) {}

    /**
     * The class must implement this method which reads a message
     * from a client.
     *
     * @param conn      Connection to a client.
     * @param message   Message.
     * @param tableNum  Table number of message.
     */
    public void parseTableMessage(ServerTableHandler conn,
                                  XMLElement message, String tableNum) {}

    /**
     * Adaper method for keeping the server model in synch with the other clients
     * when a key / value property is sent from one client to another.
     *
     * @param model  Server model which needs updated.
     * @param key    String key.
     * @param value
     */
    public void receiveProperty(GameModel model, String key, String value) {}

    /**
     * Adaper method for keeping the server model in synch with the other clients
     * when a key / int value property is sent from one client to another.
     *
     * @param model  Server model which needs updated.
     * @param key    String key.
     * @param value  Value.
     */
    public void receiveProperty(GameModel model, String key, int value) {}

    /**
     * Adaper method for keeping the server model in synch with the other clients
     * when a key / int x, int y value property is sent from one client to another.
     *
     * @param model  Server model which needs updated.
     * @param key    String key.
     * @param x      Integer x value.
     * @param y 	     Integer y value.
     */
    public void receiveProperty(GameModel model, String key, int x, int y) {}

    /**
     * Adaper method for keeping the server model in synch with the other clients
     * when an object property is sent from one client to another.
     *
     * @param model    Server model which needs updated.
     * @param message  More complex object value.
     * @param table    Table number if the user needs a handle of that.
     */
    public void receiveObject(GameModel model, XMLElement message,
                              String tableNum) {
        receiveObject(model, message);
    }

    /**
     * Adaper method for keeping the server model in synch with the other clients
     * when an object property is sent from one client to another.
     *
     * @param model    Server model which needs updated.
     * @param message  More complex object value.
     */
    public void receiveObject(GameModel model, XMLElement message) {}

    /**
     * Send a normal String valued property.
     *
     * @param conn
     * @param key
     * @param value
     */
    public void sendProperty(ServerTableHandler conn, String key,
                             String value) {
        // Create property communications object and send to server
        MsgControllerProperty commContProp = new MsgControllerProperty(
                MsgControllerProperty.TYPE_STRING, key, value);

        conn.serverSend(commContProp);
    }

    /**
     * Send a single integer property.
     *
     * @param conn
     * @param key
     * @param value
     */
    public void sendProperty(ServerTableHandler conn, String key, int value) {
        // Create property communications object and send to server
        String valueStr = String.valueOf(value);
        MsgControllerProperty commContProp = new MsgControllerProperty(
                MsgControllerProperty.TYPE_INT, key, valueStr);
        conn.serverSend(commContProp);
    }

    /**
     * Send a co-ordinate property (x and y integer values).
     *
     * @param conn
     * @param key
     * @param x
     * @param y
     */
    public void sendProperty(ServerTableHandler conn, String key, int x,
                             int y) {
        // Create property communications object and send to server
        MsgControllerProperty commContProp = new MsgControllerProperty(
                MsgControllerProperty.TYPE_INT_TWO, key, x + " " + y);
        conn.serverSend(commContProp);
    }

    /**
     * Return model associated with this table.
     *
     * @param tableNum  Specified table number.
     * @return          Current model.
     */
    public GameModel getModel(String tableNum) {
        Table table = getTable(tableNum);
        if (table != null) {
            return table.getModel();
        }

        return null;
    }

    /**
     * Sets model associated with this table.
     *
     * @param tableNum	Specified table number.
     * @param model		Current model.
     */
    public void setModel(String tableNum, GameModel model) {
        Table table = getTable(tableNum);
        if (table != null) {
            table.setModel(model);
        }
    }

    /**
     * Return a table based on a table number.
     *
     * @param tableNum   Specified table number.
     * @return           Current table.
     */
    public Table getTable(String tableNum) {
        return contr.getUserTable().getTable();
    }

    /**
     * Return the seat number of a specified player.
     *
     * @param username	Username of the player.
     * @param tableNum  Table number of current table.
     * @return          Number of specified players seat.
     */
    protected int getSeatNum(long username, String tableNum) {
        Table table = this.getTable(tableNum);
        if (table != null) {
            PlayerList playerList = table.getPlayerList();
            if (playerList != null) {
                Player player = playerList.getPlayer(username);
                if (player != null) {
                    return player.getSeatNum();
                }
            }
        }
        return Player.NOT_SEATED;
    }

    /**
     * Get player name from a seat number - does opposite of getSeatNum.
     *
     * @param seatNum    Seat number of player.
     * @param tableNum   Table number of player.
     * @return
     */
    protected long getPlayerName(int seatNum, String tableNum) {
        Table table = this.getTable(tableNum);
        if (table != null) {
            PlayerList playerList = table.getPlayerList();
            if (playerList != null) {
                Player player = playerList.getPlayer(seatNum);
                if (player != null) {
                    return player.getPlayerId();
                }
            }
        }
        return -1L;
    }

    /**
     * Return server connection thread for a username / game id.
     *
     * @param username
     * @return
     */
    protected ServerTableHandler getTableController(long username) {
//        return server.getConnections().getTableController(gameId,
//                username);
        return null;
    }

    /**
     * Return true/false if the game is started or not.
     *
     * @return  Returns true if a game is underway.
     */
    public boolean isGamePlaying(String tableNum) {
        Table table = this.getTable(tableNum);
        if (table != null) {
            return table.isGamePlaying();
        }
        return false;
    }

    /**
     * Method for declaring that a game is over which takes
     * indivual players and their results.
     *
     * @param conn        Connection to server.
     * @param tableNum    Table number.
     * @param players     List of players.
     * @param score       Optional score.
     * @param history     Optional history.
     * @param resultType  List of results.
     */
    public void gameOver(ServerTableHandler conn,
                         String tableNum,
                         long[] players,
                         int[] results,
                         String score,
                         String history) {
        try {
            //  Retrieve table object
            Table table = getTable(tableNum);

            // Create game info object.
            GameInfo gameInfo = new GameInfo(
                    getTable("").getGame().getKey(), // Game key (e.g. chess)
                    DGUtils.valueOf(players), // In game players
                    DGUtils.valueOf(results), // Results
                    table.getStartTime(), // Start time
                    new Date(), // End time (current time)
                    score, // Optional score
                    history // Optional history
                                );

            // Add game info object
            GameOver gameOver = GameServer.getInstance().getServerData().
                                addGame(gameInfo, true);

            // Declare game over
            table.gameOver();

            // inform everyone at this table that the game is up...
            MsgGameOver commGameOver = new MsgGameOver(table.getTableNum(),
                    gameOver);
            conn.transmitToTablePlayers(tableNum, commGameOver);
            conn.sendDataMessageToAdmin(commGameOver);
        } catch (Exception sdEx) {
            conn.serverSend(new MsgError(IError.SERVER_DATA_ERROR,
                                         sdEx.getMessage()));
        }
    }

    /**
     * Method for declaring that a game is over which takes
     * indivual players and their results.
     *
     * This game over doesn't declare a game score or its history
     * and is used for simplier games.
     *
     * @param conn        Connection to server.
     * @param tableNum    Table number.
     * @param players     List of players.
     * @param resultType  List of results.
     */
    public void gameOver(ServerTableHandler conn,
                         String tableNum,
                         long[] players,
                         int[] results) {
        gameOver(conn, tableNum, players, results, null, null);
    }

    /**
     * Method for declaring that a game is over which takes a
     * single player and the result type.  The other players are
     * worked out and their results automatically.
     *
     * @param conn        Connection to server.
     * @param tableNum    Table number.
     * @param username    Username of player who has won/lost
     * @param resultType  Result type.
     * @param score       Optional score.
     * @param history     Optional history.
     */
    public void gameOver(ServerTableHandler conn,
                         String tableNum,
                         long username,
                         int resultType,
                         String score,
                         String history) {
        // Retreive table object
        Table table = getTable(tableNum);

        // Retreive players and create result player if
        // result type was not a draw
        long[] players = table.getPlayerList().getInGamePlayers();
        int[] results = new int[players.length];

        if (resultType == IGameOver.DRAW) {
            for (int i = 0; i < results.length; i++) {
                results[i] = IGameOver.DRAW;
            }
        } else {
            for (int i = 0; i < results.length; i++) {
                if (resultType == IGameOver.WIN) {
                    if (username == (players[i])) {
                        results[i] = IGameOver.WIN;
                    } else {
                        results[i] = IGameOver.LOSE;
                    }
                } else if (resultType == IGameOver.LOSE) {
                    if (username == (players[i])) {
                        results[i] = IGameOver.LOSE;
                    } else {
                        results[i] = IGameOver.WIN;
                    }
                }
            }
        }

        gameOver(conn, tableNum, players, results, score, history);
    }

    /**
     * Method for declaring that a game is over which takes a
     * single player and the result type.  The other players are
     * worked out and their results automatically.
     *
     * This game over doesn't declare a game score or its history
     * and is used for simplier games.
     *
     * @param conn        Connection to server.
     * @param tableNum    Table number.
     * @param username    Username of player who has won/lost
     * @param resultType  Result type.
     */
    public void gameOver(ServerTableHandler conn,
                         String tableNum,
                         long username,
                         int resultType) {
        gameOver(conn, tableNum, username, resultType, null, null);
    }

}
